/****************************************************************************
 * Music Scale Play
 * ================
 * This program plays the basic notes (do, re, mi,...) on a piezo buzzer.
 * The notes are played forward, then backwards repeatedly.
 * Each note has a designated LED that lights up when that note is played. 
 * The speed of playback (how long each note lasts) can be altered using
 * a potentiometer. The playback can be paused/continued using a button.
 *
 * Refer to the schematic at the SVN location. 
 * 
 * The program is written for both an Arduino and an ATTINY85. 
 * Just uncomment the pin definitions for the microcontroller of choice. 
 *
 * Author: Ikwuagwu David Emole
 * Date: August 18, 2013
 * 
 ******************************************************************************/
 
 
 /*
 *  PIN DEFINITIONS:
 *  speakerPin - PWM output pin for piezo buzzer
 *  analogSpeedPin - analog input pin for potentiometer
 *  ledPin - LED pin (if using only one)
 *  pauseButtonPin - input pin for the play/pause button
 */


/*************** Arduino Pins  *********************/
//int speakerPin = 9;
//int analogSpeedPin = A0; 
//int pauseButtonPin = 7;
//int shiftreg_latchPin = 8;
//int shiftreg_clockPin = 12;
//int shiftreg_dataPin = 11;



/*************** ATTiny85 Pins  *********************/
int speakerPin = 0;
int analogSpeedPin = 3;
int shiftreg_latchPin = 1;
int shiftreg_clockPin = 4;
int shiftreg_dataPin = 2;


/***** Note Definitions - C D E F G A B C ******/
int _do = 261;
int _re = 294;
int _mi = 330;
int _fa = 349;
int _so = 392;
int _la = 440;
int _ti = 493;
int _Do = 523;

/***** My Music ********/
int notes[] = {_do, _re, _mi, _fa, _so, _la, _ti, _Do};                      // array of notes
int noteCount = (sizeof(notes)/sizeof(int));


/********* Delay Control Parameters *******/
int maxDelay = 2000;  // maximum time between notes
int minDelay = 50;  // the lowest possible delay (in case potentiometer is turned to 0) 
int delayTime;  // actual time between notes (recalculated based on potentiometer reading)
float delayFactor = (maxDelay / 1023.0);  // Factor used to convert analog reading to delay time


/******** Music loop control parameters *****/
int currentNote = 0;
int previousNote = 0;
int limit = noteCount - 1;
boolean FORWARD = true;   // When true, notes are played front-to-back. Else, they're played back-to-front.


/****** Time Control parameters *******/
unsigned long currentMillis = 0;
unsigned long previousMillis = 0;
boolean timeToPlayNext = true;

/****** Playing State ****/
boolean keepPlaying = true;
boolean timeToRest = false;

void setup()
{
  pinMode(shiftreg_latchPin, OUTPUT);
  pinMode(shiftreg_dataPin, OUTPUT);  
  pinMode(shiftreg_clockPin, OUTPUT);
}


void loop()
{
    // Is it time to play the next note
    if(timeToPlayNext)
    {
      // Since we are looping back and forth, we rest once we get to each end. 
      if(timeToRest)
      {
        rest();
        timeToRest = false;
      }
      else
      {
        // Play current note
        tone(speakerPin, notes[currentNote]);
  
        // Turn on the LED for this note
        lightOn(currentNote);      
        
        // Set the note you just played as previous note
        previousNote = currentNote;
      
        // Go to the next note, depending on whether the current direction is forward or backwards
        if(FORWARD) 
        { 
          currentNote++;
          if(currentNote > limit) 
          { 
            FORWARD = false;
            currentNote = noteCount - 1;
            limit = 0;
          } 
        } 
        else
        {
          currentNote--;
          if(currentNote < limit)
          {
            FORWARD = true;
            currentNote = 0;
            limit = noteCount - 1;
          }
        }
        
        // If the next note is the same with the previous, it means 
        // we are about to play in the reverse direction. Rest before starting again. 
        if(previousNote == currentNote)
        {
          timeToRest = true;
        }
      }    
    }
    
    // Recalculate delay time
    calculateDelay();

    // Check if it's time to play next
    currentMillis = millis();
    if(currentMillis - previousMillis >= delayTime)
    {
      timeToPlayNext = true;
      previousMillis = currentMillis;
    }
    else
    {
      timeToPlayNext = false;
    }

}

void calculateDelay()
{
  // Convert analog  input (0 - 1023) to time delay (0 - max specified delay)
  int analogValue = analogRead(analogSpeedPin);
  delayTime = (analogValue * delayFactor) + minDelay;
}

void rest()
{
  noTone(speakerPin);
  lightOff(currentNote);
}


void lightOn(int noteNumber)
{
  registerWrite(noteNumber, HIGH);
}

void lightOff(int noteNumber)
{
  registerWrite(noteNumber, LOW);
}


void registerWrite(int whichPin, int whichState) 
{
  byte bitsToSend = 0;

  // turn off the output so the pins don't light up
  // while you're shifting bits:
  digitalWrite(shiftreg_latchPin, LOW);

  // turn on the next highest bit in bitsToSend:
  bitWrite(bitsToSend, whichPin, whichState);

  // shift the bits out:
  shiftOut(shiftreg_dataPin, shiftreg_clockPin, MSBFIRST, bitsToSend);

  // turn on the output so the LEDs can light up:
  digitalWrite(shiftreg_latchPin, HIGH);

}
